/**
 * Copyright (c) 2017, Alex. All rights reserved.
 */
package com.oschina.bottle.redis.lock;

import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

/**
 * @author <a href="mailto:zhongchao@gegejia.com">zhong</a>
 * @version 1.0 2017/11/14
 * @since 1.0
 */
public class RedisPessimisticLock extends AbstractRedisLock
{
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 所名称
     */
    private String lockKey;

    /**
     * 锁超时时长
     */
    private long lockExpire;

    public RedisPessimisticLock(RedisTemplate<String, String> redisTemplate, String lockKey, long lockExpire)
    {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.lockExpire = lockExpire;
    }

    public boolean isLocked() {
        if(locked)
            return true;
        else {
            String value = redisTemplate.boundValueOps(lockKey).get();

            return !isTimeExpired(value);
        }
    }

    @Override
    protected void unlock0()
    {
        //判断锁是否过期
        String value = redisTemplate.boundValueOps(lockKey).get();

        if(!isTimeExpired(value)) {
            doUnlock();
        }
    }

    /**
     * 阻塞式悲观锁实现
     * 利用setnx、getset实现
     *
     * @param useTimeout    获取锁超时设置
     * @param time          超时时间
     * @param unit          超时时间单位
     * @param interrupt
     *            是否响应中断
     * @return
     * @throws InterruptedException
     */
    @Override
    protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt)
        throws InterruptedException
    {
        if(interrupt)
            checkInterruption();

        long start = System.currentTimeMillis();
        long timeout = unit.toMillis(time);

        //判断 lock是否过期
        while (useTimeout ? isTimeout(start, timeout) : true) {
            if(tryLock())
                return true;
        }

        return false;
    }

    @Override
    public boolean tryLock()
    {
        long lockExpireTime = System.currentTimeMillis() + lockExpire + 1; //锁超时时间

        String strOfLockExpireTime = String.valueOf(lockExpireTime);

        //获取锁
        if (redisTemplate.boundValueOps(lockKey).setIfAbsent(strOfLockExpireTime)) {

            //获取到锁标识
            locked = true;

            // 设置当前获取锁的线程
            setOwnerThread(Thread.currentThread());
            return true;
        }

        // 获取不到锁处理
        String value = redisTemplate.boundValueOps(lockKey).get();

        if(value != null && isTimeExpired(value)) { //锁过期. 如果锁没过期，则进入下次循环

            //假设多个线程总到此处, getAndSet使用redis的getSet方法（原子性的）。返回原先的旧值，如果获取到的旧值依然是过期，则表示获取到锁
            String oldValue = redisTemplate.boundValueOps(lockKey).getAndSet(strOfLockExpireTime);

            if(oldValue != null && oldValue.equals(value)) {
                locked = true;

                setOwnerThread(Thread.currentThread());

                return true;
            }
        }

        return false;
    }

    @Override
    public Condition newCondition()
    {
        return null;
    }

    private void checkInterruption() throws InterruptedException {
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException();
        }
    }

    private boolean isTimeExpired(String value) {
        return Long.parseLong(value) < System.currentTimeMillis();
    }

    private boolean isTimeout(long start, long timeout) {
        return start + timeout > System.currentTimeMillis();
    }

    private void doUnlock() {
        redisTemplate.delete(lockKey);
        setLocked(locked);
        setOwnerThread(null);
    }

}
